Skip to main content

aviutl2\generic\binding/
edit_handle.rs

1use std::num::NonZeroIsize;
2
3use crate::common::{ChildKillablePointer, KillablePointer};
4use crate::generic::{EditSection, ReadSection};
5
6/// 編集ハンドル。
7///
8/// # Panics
9///
10/// [`crate::generic::GenericPlugin::register`]が終了するまでは、以下のメソッド以外は呼び出せません。
11/// - [`Self::get_host_app_window`]
12/// - [`Self::get_host_app_window_raw`]
13/// - [`Self::is_ready`]
14#[derive(Debug)]
15pub struct EditHandle {
16    pub(crate) internal: *mut aviutl2_sys::plugin2::EDIT_HANDLE,
17    pub(crate) is_ready: std::sync::Arc<std::sync::atomic::AtomicBool>,
18}
19
20unsafe impl Send for EditHandle {}
21unsafe impl Sync for EditHandle {}
22
23/// [`EditHandle`] 関連のエラー。
24#[derive(thiserror::Error, Debug)]
25pub enum EditHandleError {
26    #[error("api call failed")]
27    ApiCallFailed,
28    #[error("effect does not exist")]
29    EffectNotFound,
30    #[error("input utf-16 string contains null byte")]
31    InputCwstrContainsNull(#[from] crate::common::NullByteError),
32    #[error("unknown edit state: {0}")]
33    UnknownEditState(i32),
34}
35
36impl EditHandle {
37    pub(crate) unsafe fn new(
38        internal: *mut aviutl2_sys::plugin2::EDIT_HANDLE,
39        is_ready: std::sync::Arc<std::sync::atomic::AtomicBool>,
40    ) -> Self {
41        Self { internal, is_ready }
42    }
43
44    /// 編集ハンドルが使用可能かどうかを確認します。
45    pub fn is_ready(&self) -> bool {
46        self.is_ready.load(std::sync::atomic::Ordering::Acquire)
47    }
48
49    /// プロジェクトデータの編集を開始する。
50    ///
51    /// # Note
52    ///
53    /// 内部では call_edit_section_param を使用しています。
54    pub fn call_edit_section<'a, T, F>(&self, callback: F) -> Result<T, EditHandleError>
55    where
56        T: Send + 'static,
57        F: FnOnce(&mut EditSection) -> T + Send + 'a,
58    {
59        assert!(
60            self.is_ready(),
61            "call_edit_section cannot be called before register_plugin is done"
62        );
63
64        type CallbackParam<'a, F, T> = (ChildKillablePointer<Option<F>>, &'a mut Option<T>);
65
66        let closure = Some(callback);
67        let param = KillablePointer::new(closure);
68        let child_param = param.create_child();
69
70        extern "C" fn trampoline<F, T>(
71            param: *mut std::ffi::c_void,
72            edit_section: *mut aviutl2_sys::plugin2::EDIT_SECTION,
73        ) where
74            T: Send + 'static,
75            F: FnOnce(&mut EditSection) -> T,
76        {
77            unsafe {
78                let (child_param, result_ptr) = &mut *(param as *mut CallbackParam<F, T>);
79                let callback = child_param.as_mut().take().expect("Callback already taken");
80                let mut edit_section = EditSection::from_raw(edit_section);
81                let res = callback(&mut edit_section);
82                result_ptr.replace(res);
83            }
84        }
85
86        let trampoline_static = trampoline::<F, T>
87            as extern "C" fn(*mut std::ffi::c_void, *mut aviutl2_sys::plugin2::EDIT_SECTION);
88
89        let mut result = None;
90        let param = Box::<CallbackParam<F, T>>::new((child_param, &mut result));
91        let param_ptr = Box::into_raw(param);
92
93        let success = unsafe {
94            ((*self.internal).call_edit_section_param)(
95                param_ptr as *mut std::ffi::c_void,
96                trampoline_static,
97            )
98        };
99
100        drop(unsafe { Box::from_raw(param_ptr) });
101
102        if success {
103            Ok(result.expect("Callback did not set result"))
104        } else {
105            Err(EditHandleError::ApiCallFailed)
106        }
107    }
108
109    /// プロジェクトデータの参照を開始する。
110    ///
111    /// # Note
112    ///
113    /// 内部では call_read_section_param を使用しています。
114    pub fn call_read_section<'a, T, F>(&self, callback: F) -> Result<T, EditHandleError>
115    where
116        T: Send + 'static,
117        F: FnOnce(&ReadSection) -> T + Send + 'a,
118    {
119        assert!(
120            self.is_ready(),
121            "call_read_section cannot be called before register_plugin is done"
122        );
123
124        type CallbackParam<'a, F, T> = (ChildKillablePointer<Option<F>>, &'a mut Option<T>);
125
126        let closure = Some(callback);
127        let param = KillablePointer::new(closure);
128        let child_param = param.create_child();
129
130        extern "C" fn trampoline<F, T>(
131            param: *mut std::ffi::c_void,
132            read_section: *mut aviutl2_sys::plugin2::EDIT_SECTION,
133        ) where
134            T: Send + 'static,
135            F: FnOnce(&ReadSection) -> T,
136        {
137            unsafe {
138                let (child_param, result_ptr) = &mut *(param as *mut CallbackParam<F, T>);
139                let callback = child_param.as_mut().take().expect("Callback already taken");
140                let read_section = ReadSection::from_raw(read_section);
141                let res = callback(&read_section);
142                result_ptr.replace(res);
143            }
144        }
145
146        let trampoline_static = trampoline::<F, T>
147            as extern "C" fn(*mut std::ffi::c_void, *mut aviutl2_sys::plugin2::EDIT_SECTION);
148
149        let mut result = None;
150        let param = Box::<CallbackParam<F, T>>::new((child_param, &mut result));
151        let param_ptr = Box::into_raw(param);
152
153        let success = unsafe {
154            ((*self.internal).call_read_section_param)(
155                param_ptr as *mut std::ffi::c_void,
156                trampoline_static,
157            )
158        };
159
160        drop(unsafe { Box::from_raw(param_ptr) });
161
162        if success {
163            Ok(result.expect("Callback did not set result"))
164        } else {
165            Err(EditHandleError::ApiCallFailed)
166        }
167    }
168
169    /// 編集情報を取得する。
170    pub fn get_edit_info(&self) -> crate::generic::EditInfo {
171        assert!(
172            self.is_ready(),
173            "get_edit_info cannot be called before register_plugin is done"
174        );
175        let mut raw_info = std::mem::MaybeUninit::<aviutl2_sys::plugin2::EDIT_INFO>::uninit();
176        unsafe {
177            ((*self.internal).get_edit_info)(
178                raw_info.as_mut_ptr(),
179                std::mem::size_of::<aviutl2_sys::plugin2::EDIT_INFO>() as _,
180            );
181            let edit_info = raw_info.assume_init();
182            crate::generic::EditInfo::from_raw(&edit_info)
183        }
184    }
185
186    /// ホストアプリケーションを再起動する。
187    pub fn restart_host_app(&self) {
188        assert!(
189            self.is_ready(),
190            "restart_host_app cannot be called before register_plugin is done"
191        );
192        unsafe {
193            ((*self.internal).restart_host_app)();
194        }
195    }
196
197    /// エフェクトの一覧をコールバック関数で取得する。
198    ///
199    /// # Note
200    ///
201    /// 不明なエフェクト種別があった場合はスキップされます。
202    pub fn enumerate_effects<F>(&self, callback: F)
203    where
204        F: FnMut(Effect),
205    {
206        assert!(
207            self.is_ready(),
208            "enumerate_effects cannot be called before register_plugin is done"
209        );
210        type CallbackParam<F> = ChildKillablePointer<F>;
211
212        extern "C" fn trampoline<F>(
213            param: *mut std::ffi::c_void,
214            name: aviutl2_sys::common::LPCWSTR,
215            r#type: i32,
216            flag: i32,
217        ) where
218            F: FnMut(Effect),
219        {
220            let callback = unsafe { &mut *(param as *mut CallbackParam<F>) };
221            let callback = unsafe { callback.as_mut() };
222            let name_str = unsafe { crate::common::load_wide_string(name) };
223            if let Ok(effect_type) = EffectType::try_from(r#type) {
224                let effect = Effect {
225                    name: name_str,
226                    effect_type,
227                    flag: EffectFlag::from_bits(flag),
228                };
229                callback(effect);
230            } else {
231                tracing::warn!("Unknown effect type: {}", r#type);
232            }
233        }
234
235        let trampoline_static = trampoline::<F>
236            as extern "C" fn(*mut std::ffi::c_void, aviutl2_sys::common::LPCWSTR, i32, i32);
237        let callback_guard = KillablePointer::new(callback);
238        let child_param = callback_guard.create_child();
239        let param = Box::new(child_param);
240        let param_ptr = Box::into_raw(param);
241        unsafe {
242            ((*self.internal).enum_effect_name)(
243                param_ptr as *mut std::ffi::c_void,
244                trampoline_static,
245            );
246        }
247        drop(unsafe { Box::from_raw(param_ptr) });
248    }
249
250    /// エフェクトの一覧を取得する。
251    pub fn get_effects(&self) -> Vec<Effect> {
252        assert!(
253            self.is_ready(),
254            "get_effects cannot be called before register_plugin is done"
255        );
256        let mut effects = Vec::new();
257        self.enumerate_effects(|effect| {
258            effects.push(effect);
259        });
260        effects
261    }
262
263    /// エフェクトの設定項目一覧をコールバック関数で取得する。
264    ///
265    /// # Arguments
266    ///
267    /// - `effect`: 対象のエフェクト名。エイリアスファイルの `effect.name` を指定します。
268    ///
269    /// # Note
270    ///
271    /// 不明な設定項目種別があった場合はスキップされます。
272    pub fn enumerate_effect_items<F>(
273        &self,
274        effect: &str,
275        callback: F,
276    ) -> Result<(), EditHandleError>
277    where
278        F: FnMut(EffectItemInfo),
279    {
280        assert!(
281            self.is_ready(),
282            "enumerate_effect_items cannot be called before register_plugin is done"
283        );
284        type CallbackParam<F> = ChildKillablePointer<F>;
285
286        unsafe extern "C" fn trampoline<F>(
287            param: *mut std::ffi::c_void,
288            name: aviutl2_sys::common::LPCWSTR,
289            r#type: i32,
290        ) where
291            F: FnMut(EffectItemInfo),
292        {
293            let callback = unsafe { &mut *(param as *mut CallbackParam<F>) };
294            let callback = unsafe { callback.as_mut() };
295            let name = unsafe { crate::common::load_wide_string(name) };
296            if let Some(info) = effect_item_info_from_raw(name, r#type) {
297                callback(info);
298            }
299        }
300
301        let effect = crate::common::CWString::new(effect)?;
302        let trampoline_static = trampoline::<F>
303            as unsafe extern "C" fn(*mut std::ffi::c_void, aviutl2_sys::common::LPCWSTR, i32);
304        let callback_guard = KillablePointer::new(callback);
305        let child_param = callback_guard.create_child();
306        let param = Box::new(child_param);
307        let param_ptr = Box::into_raw(param);
308        let success = unsafe {
309            ((*self.internal).enum_effect_item)(
310                effect.as_ptr(),
311                param_ptr as *mut std::ffi::c_void,
312                trampoline_static,
313            )
314        };
315        drop(unsafe { Box::from_raw(param_ptr) });
316
317        if success {
318            Ok(())
319        } else {
320            Err(EditHandleError::EffectNotFound)
321        }
322    }
323
324    /// エフェクトの設定項目一覧を取得する。
325    ///
326    /// # Arguments
327    ///
328    /// - `effect`: 対象のエフェクト名。エイリアスファイルの `effect.name` を指定します。
329    pub fn get_effect_items(&self, effect: &str) -> Result<Vec<EffectItemInfo>, EditHandleError> {
330        assert!(
331            self.is_ready(),
332            "get_effect_items cannot be called before register_plugin is done"
333        );
334        let mut items = Vec::new();
335        self.enumerate_effect_items(effect, |item| {
336            items.push(item);
337        })?;
338        Ok(items)
339    }
340
341    /// モジュールの一覧をコールバック関数で取得する。
342    pub fn enumerate_modules<F>(&self, callback: F)
343    where
344        F: FnMut(ModuleInfo),
345    {
346        assert!(
347            self.is_ready(),
348            "enumerate_modules cannot be called before register_plugin is done"
349        );
350        type CallbackParam<F> = ChildKillablePointer<F>;
351
352        extern "C" fn trampoline<F>(
353            param: *mut std::ffi::c_void,
354            module: *mut aviutl2_sys::plugin2::MODULE_INFO,
355        ) where
356            F: FnMut(ModuleInfo),
357        {
358            let callback = unsafe { &mut *(param as *mut CallbackParam<F>) };
359            let callback = unsafe { callback.as_mut() };
360            if let Some(module_info) = module_info_from_raw(module) {
361                callback(module_info);
362            }
363        }
364        let trampoline_static = trampoline::<F>
365            as unsafe extern "C" fn(*mut std::ffi::c_void, *mut aviutl2_sys::plugin2::MODULE_INFO);
366        let callback_guard = KillablePointer::new(callback);
367        let child_param = callback_guard.create_child();
368        let param = Box::new(child_param);
369        let param_ptr = Box::into_raw(param);
370        unsafe {
371            ((*self.internal).enum_module_info)(
372                param_ptr as *mut std::ffi::c_void,
373                trampoline_static,
374            );
375        }
376        drop(unsafe { Box::from_raw(param_ptr) });
377    }
378
379    /// モジュールの一覧を取得する。
380    pub fn get_modules(&self) -> Vec<ModuleInfo> {
381        assert!(
382            self.is_ready(),
383            "get_modules cannot be called before register_plugin is done"
384        );
385        let mut modules = Vec::new();
386        self.enumerate_modules(|module| {
387            modules.push(module);
388        });
389        modules
390    }
391
392    /// ホストアプリケーションのメインウィンドウのハンドルを[`raw_window_handle::Win32WindowHandle`]として取得する。
393    pub fn get_host_app_window_raw(&self) -> Option<raw_window_handle::Win32WindowHandle> {
394        let hwnd = unsafe { ((*self.internal).get_host_app_window)() };
395        NonZeroIsize::new(hwnd as isize).map(raw_window_handle::Win32WindowHandle::new)
396    }
397
398    /// ホストアプリケーションのメインウィンドウのハンドルを[`raw_window_handle::WindowHandle`]として取得する。
399    ///
400    /// # Safety
401    ///
402    /// [`raw_window_handle::WindowHandle::borrow_raw`] を参照してください。
403    pub unsafe fn get_host_app_window(&'_ self) -> Option<raw_window_handle::WindowHandle<'_>> {
404        self.get_host_app_window_raw().map(|handle| unsafe {
405            raw_window_handle::WindowHandle::borrow_raw(raw_window_handle::RawWindowHandle::Win32(
406                handle,
407            ))
408        })
409    }
410
411    /// 編集状態を取得する。
412    pub fn get_edit_state(&self) -> Result<EditState, EditHandleError> {
413        assert!(
414            self.is_ready(),
415            "get_edit_state cannot be called before register_plugin is done"
416        );
417        let state = unsafe { ((*self.internal).get_edit_state)() };
418        EditState::try_from(state).map_err(|_| EditHandleError::UnknownEditState(state))
419    }
420}
421
422/// エフェクト情報。
423#[derive(Debug, Clone, PartialEq, Eq)]
424pub struct Effect {
425    /// エフェクト名。
426    pub name: String,
427    /// エフェクト種別。
428    pub effect_type: EffectType,
429    /// フラグ。
430    pub flag: EffectFlag,
431}
432
433/// エフェクトの設定項目情報。
434#[derive(Debug, Clone, PartialEq, Eq)]
435pub struct EffectItemInfo {
436    /// 設定項目名。
437    pub name: String,
438    /// 設定項目種別。
439    pub item_type: EffectItemType,
440}
441
442/// エフェクト種別。
443#[derive(Debug, Clone, Copy, PartialEq, Eq)]
444pub enum EffectType {
445    /// フィルタ効果。
446    Filter,
447    /// メディア入力。
448    Input,
449    /// シーンチェンジ。
450    SceneChange,
451    /// オブジェクト制御。
452    Control,
453    /// メディア出力。
454    Output,
455}
456
457/// 設定項目種別。
458#[derive(Debug, Clone, Copy, PartialEq, Eq)]
459pub enum EffectItemType {
460    /// 整数。
461    Integer,
462    /// 数値。
463    Number,
464    /// チェックボックス。
465    Check,
466    /// テキスト。
467    Text,
468    /// 文字列。
469    String,
470    /// ファイル。
471    File,
472    /// 色。
473    Color,
474    /// リスト選択。
475    Select,
476    /// シーン。
477    Scene,
478    /// レイヤー範囲。
479    Range,
480    /// リストと文字の複合。
481    Combo,
482    /// マスク。
483    Mask,
484    /// フォント。
485    Font,
486    /// 図形。
487    Figure,
488    /// データ。
489    Data,
490    /// フォルダ。
491    Folder,
492}
493
494define_bitflag! {
495    /// エフェクトのフラグ。
496    #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
497    #[non_exhaustive]
498    pub struct EffectFlag: i32 {
499        /// 画像フィルタをサポートするかどうか。
500        video: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_VIDEO,
501
502        /// 音声フィルタをサポートするかどうか。
503        audio: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_AUDIO,
504
505        /// フィルタオブジェクトをサポートするかどうか。
506        as_filter: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_FILTER,
507
508        /// カメラ効果をサポートするかどうか。
509        camera: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_CAMERA,
510    }
511}
512
513impl TryFrom<i32> for EffectType {
514    type Error = ();
515
516    fn try_from(value: i32) -> Result<Self, Self::Error> {
517        match value {
518            1 => Ok(EffectType::Filter),
519            2 => Ok(EffectType::Input),
520            3 => Ok(EffectType::SceneChange),
521            4 => Ok(EffectType::Control),
522            5 => Ok(EffectType::Output),
523            _ => Err(()),
524        }
525    }
526}
527impl From<EffectType> for i32 {
528    fn from(value: EffectType) -> Self {
529        match value {
530            EffectType::Filter => 1,
531            EffectType::Input => 2,
532            EffectType::SceneChange => 3,
533            EffectType::Control => 4,
534            EffectType::Output => 5,
535        }
536    }
537}
538
539impl TryFrom<i32> for EffectItemType {
540    type Error = ();
541
542    fn try_from(value: i32) -> Result<Self, Self::Error> {
543        match value {
544            1 => Ok(EffectItemType::Integer),
545            2 => Ok(EffectItemType::Number),
546            3 => Ok(EffectItemType::Check),
547            4 => Ok(EffectItemType::Text),
548            5 => Ok(EffectItemType::String),
549            6 => Ok(EffectItemType::File),
550            7 => Ok(EffectItemType::Color),
551            8 => Ok(EffectItemType::Select),
552            9 => Ok(EffectItemType::Scene),
553            10 => Ok(EffectItemType::Range),
554            11 => Ok(EffectItemType::Combo),
555            12 => Ok(EffectItemType::Mask),
556            13 => Ok(EffectItemType::Font),
557            14 => Ok(EffectItemType::Figure),
558            15 => Ok(EffectItemType::Data),
559            16 => Ok(EffectItemType::Folder),
560            _ => Err(()),
561        }
562    }
563}
564impl From<EffectItemType> for i32 {
565    fn from(value: EffectItemType) -> Self {
566        match value {
567            EffectItemType::Integer => 1,
568            EffectItemType::Number => 2,
569            EffectItemType::Check => 3,
570            EffectItemType::Text => 4,
571            EffectItemType::String => 5,
572            EffectItemType::File => 6,
573            EffectItemType::Color => 7,
574            EffectItemType::Select => 8,
575            EffectItemType::Scene => 9,
576            EffectItemType::Range => 10,
577            EffectItemType::Combo => 11,
578            EffectItemType::Mask => 12,
579            EffectItemType::Font => 13,
580            EffectItemType::Figure => 14,
581            EffectItemType::Data => 15,
582            EffectItemType::Folder => 16,
583        }
584    }
585}
586
587/// モジュール情報。
588#[derive(Debug, Clone, PartialEq, Eq)]
589pub struct ModuleInfo {
590    /// モジュール種別。
591    pub module_type: ModuleType,
592    /// 名前。
593    pub name: String,
594    /// 情報。
595    pub information: String,
596}
597
598/// モジュール種別。
599#[derive(Debug, Clone, Copy, PartialEq, Eq)]
600pub enum ModuleType {
601    /// フィルタスクリプト。
602    ScriptFilter,
603    /// オブジェクトスクリプト。
604    ScriptObject,
605    /// カメラスクリプト。
606    ScriptCamera,
607    /// トラックバースクリプト。
608    ScriptTrack,
609    /// スクリプトモジュール。
610    ScriptModule,
611    /// 入力プラグイン。
612    PluginInput,
613    /// 出力プラグイン。
614    PluginOutput,
615    /// フィルタプラグイン。
616    PluginFilter,
617    /// 汎用プラグイン。
618    PluginGeneric,
619}
620
621impl TryFrom<i32> for ModuleType {
622    type Error = ();
623
624    fn try_from(value: i32) -> Result<Self, Self::Error> {
625        match value {
626            1 => Ok(ModuleType::ScriptFilter),
627            2 => Ok(ModuleType::ScriptObject),
628            3 => Ok(ModuleType::ScriptCamera),
629            4 => Ok(ModuleType::ScriptTrack),
630            5 => Ok(ModuleType::ScriptModule),
631            6 => Ok(ModuleType::PluginInput),
632            7 => Ok(ModuleType::PluginOutput),
633            8 => Ok(ModuleType::PluginFilter),
634            9 => Ok(ModuleType::PluginGeneric),
635            _ => Err(()),
636        }
637    }
638}
639impl From<ModuleType> for i32 {
640    fn from(value: ModuleType) -> Self {
641        match value {
642            ModuleType::ScriptFilter => 1,
643            ModuleType::ScriptObject => 2,
644            ModuleType::ScriptCamera => 3,
645            ModuleType::ScriptTrack => 4,
646            ModuleType::ScriptModule => 5,
647            ModuleType::PluginInput => 6,
648            ModuleType::PluginOutput => 7,
649            ModuleType::PluginFilter => 8,
650            ModuleType::PluginGeneric => 9,
651        }
652    }
653}
654
655/// 編集状態。
656#[derive(Debug, Clone, Copy, PartialEq, Eq)]
657pub enum EditState {
658    /// 編集中
659    Edit,
660    /// プレビュー再生中
661    Preview,
662    /// ファイル出力中
663    Save,
664}
665
666impl TryFrom<i32> for EditState {
667    type Error = ();
668
669    fn try_from(value: i32) -> Result<Self, Self::Error> {
670        match value {
671            0 => Ok(EditState::Edit),
672            1 => Ok(EditState::Preview),
673            2 => Ok(EditState::Save),
674            _ => Err(()),
675        }
676    }
677}
678impl From<EditState> for i32 {
679    fn from(value: EditState) -> Self {
680        match value {
681            EditState::Edit => 0,
682            EditState::Preview => 1,
683            EditState::Save => 2,
684        }
685    }
686}
687
688/// グローバルに [EditHandle] を保持するための構造体。
689///
690/// `OnceLock` と違い、もし初期化していない状態でアクセスした場合にパニックします。
691#[derive(Debug)]
692pub struct GlobalEditHandle {
693    edit_handle: std::sync::OnceLock<crate::generic::EditHandle>,
694}
695
696impl GlobalEditHandle {
697    /// 新しいインスタンスを作成する。
698    pub const fn new() -> Self {
699        Self {
700            edit_handle: std::sync::OnceLock::new(),
701        }
702    }
703
704    /// 初期化する。すでに初期化されている場合は警告をログに出力します。
705    pub fn init(&self, edit_handle: crate::generic::EditHandle) {
706        let _ = self
707            .edit_handle
708            .set(edit_handle)
709            .map_err(|_| tracing::warn!("GlobalEditHandle was already initialized"));
710    }
711
712    /// 初期化されているかどうかを確認します。
713    pub fn is_ready(&self) -> bool {
714        self.edit_handle
715            .get()
716            .is_some_and(|handle| handle.is_ready())
717    }
718}
719
720impl Default for GlobalEditHandle {
721    fn default() -> Self {
722        Self::new()
723    }
724}
725
726impl std::ops::Deref for GlobalEditHandle {
727    type Target = crate::generic::EditHandle;
728
729    fn deref(&self) -> &Self::Target {
730        self.edit_handle
731            .get()
732            .expect("GlobalEditHandle is not initialized")
733    }
734}
735
736fn effect_item_info_from_raw(name: String, item_type: i32) -> Option<EffectItemInfo> {
737    if let Ok(item_type) = EffectItemType::try_from(item_type) {
738        Some(EffectItemInfo { name, item_type })
739    } else {
740        tracing::warn!("Unknown effect item type: {}", item_type);
741        None
742    }
743}
744
745fn module_info_from_raw(raw: *mut aviutl2_sys::plugin2::MODULE_INFO) -> Option<ModuleInfo> {
746    let module_type = unsafe { (*raw).r#type };
747    if let Ok(module_type) = ModuleType::try_from(module_type) {
748        Some(ModuleInfo {
749            module_type,
750            name: unsafe { crate::common::load_wide_string((*raw).name) },
751            information: unsafe { crate::common::load_wide_string((*raw).information) },
752        })
753    } else {
754        tracing::warn!("Unknown module type: {}", module_type);
755        None
756    }
757}
758
759#[cfg(test)]
760mod tests {
761    use super::*;
762
763    #[test]
764    fn effect_item_type_try_from_known_values() {
765        assert_eq!(EffectItemType::try_from(1), Ok(EffectItemType::Integer));
766        assert_eq!(EffectItemType::try_from(8), Ok(EffectItemType::Select));
767        assert_eq!(EffectItemType::try_from(16), Ok(EffectItemType::Folder));
768    }
769
770    #[test]
771    fn effect_item_type_try_from_unknown_value_fails() {
772        assert_eq!(EffectItemType::try_from(999), Err(()));
773    }
774
775    #[test]
776    fn effect_item_type_into_i32() {
777        assert_eq!(i32::from(EffectItemType::Integer), 1);
778        assert_eq!(i32::from(EffectItemType::Combo), 11);
779        assert_eq!(i32::from(EffectItemType::Folder), 16);
780    }
781
782    #[test]
783    fn effect_item_info_from_raw_returns_none_for_unknown_type() {
784        assert_eq!(effect_item_info_from_raw("test".to_string(), 999), None);
785    }
786
787    #[test]
788    fn effect_item_info_from_raw_builds_info_for_known_type() {
789        assert_eq!(
790            effect_item_info_from_raw("test".to_string(), 4),
791            Some(EffectItemInfo {
792                name: "test".to_string(),
793                item_type: EffectItemType::Text,
794            })
795        );
796    }
797
798    #[test]
799    fn module_type_try_from_unknown_value_fails() {
800        assert_eq!(ModuleType::try_from(999), Err(()));
801    }
802
803    #[test]
804    fn edit_state_try_from_unknown_value_fails() {
805        assert_eq!(EditState::try_from(999), Err(()));
806    }
807}